/** \file    registry.cpp
  * \brief   Implementation of our "xml" tree manipulation engine for Windows Registry.
  * \date    2003
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/mswin/src/registry.cpp,v 1.2 2004/03/26 05:58:02 cscom1r4 Exp $
  */

#include "mswin/registry.h"
#include "throwWin32Error.h"

namespace xml {

///////////////////////
// registry::element //
///////////////////////

struct subkey {
	const string & name;
	subkey(const string & n) : name(n) {}
};

struct mytag {
	const string & name;
	mytag(const string & n) : name(n) {}
};

class registry::element {
	HKEY _hkey;
	const string _tag;
public:
	inline element(const element & src);
	inline element(HKEY hkey);
	inline element(HKEY hkey, mytag t);
	inline element(HKEY hkey, subkey s);
	inline ~element();
public:
	inline const string & tag() const;
	inline attribute_enumeration * get_attrs() const;
	inline bool get_attribute(const string & name, string & value) const;
	inline void set_attribute(const string & name, const string & value);
	inline node_enumeration * get_children();
	inline node_enumeration * get_children_by_tag(const string & tag);
	inline handle create_child(const string & tag);
	void clear(bool attr, bool children);
	inline void erase(const string & subkey);
private:
	inline element(const string & tag);
	static inline string basename(const string & subkey);
};

///////////////////
// key_attr_enum //
///////////////////

class key_attr_enum : public attribute_enumeration {
private:
	const HKEY _hkey;
	DWORD _index;
	DWORD _max_index;
	string _buffer;
public:
	key_attr_enum(HKEY hkey);
public:
	virtual void first()
		{ _index = 0; }
	virtual string current();
	virtual void next()
		{ ++_index;	}
	virtual bool end()
		{ return _index == _max_index; }
};

key_attr_enum::key_attr_enum(HKEY hkey) : _hkey(hkey) 
{
	DWORD max_value_length;
	if (ERROR_SUCCESS
		!= RegQueryInfoKey(hkey, 0, 0, 0, 0, 0, 0, &_max_index, &max_value_length, 0, 0, 0))
		throwWin32Error("RegQueryInfoKey");
	_buffer.resize(max_value_length+1);
}

string key_attr_enum::current()
{
	DWORD cValueName = _buffer.size();
	if (ERROR_SUCCESS
		!= RegEnumValue(_hkey, _index, &*_buffer.begin(), &cValueName, 0, 0, 0, 0))
		throwWin32Error("RegEnumValue");
	return _buffer.substr(0, cValueName);
}

///////////////////////
// key_children_enum //
///////////////////////

class key_children_enum : public node_enumeration {
private:
	const HKEY _hkey;
	DWORD _index;
	DWORD _max_index;
	string _buffer;
public:
	key_children_enum(HKEY hkey);
public:
	virtual void first()
		{ _index = 0; }
	virtual handle current(); 
	virtual void next()
		{ ++_index; }
	virtual bool end()
		{ return _index == _max_index; }
};

key_children_enum::key_children_enum(HKEY hkey) : 
	_hkey(hkey)
{
	DWORD max_key_length;
	if (ERROR_SUCCESS
		!= RegQueryInfoKey(hkey, 0, 0, 0, &_max_index, &max_key_length, 0, 0, 0, 0, 0, 0))
		throwWin32Error("RegQueryInfoKey");
	_buffer.resize(max_key_length+1);
}

handle key_children_enum::current()
{
	DWORD cName = _buffer.size();
	if (ERROR_SUCCESS
		!= RegEnumKeyEx(_hkey, _index, &*_buffer.begin(), &cName, 0, 0, 0, 0))
		throwWin32Error("RegEnumKeyEx");
	return new registry::element(_hkey, subkey(_buffer.substr(0, cName)));
}

//////////////////////////
// key_named_child_enum //
//////////////////////////

class key_named_child_enum : public node_enumeration {
private:
	HKEY _hchildkey;
	bool _end;
	const string _name;
public:
	key_named_child_enum(HKEY hkey, const string & name);
	~key_named_child_enum();
public:
	virtual void first()
		{ _end = false; }
	virtual handle current() 
		{ return new registry::element(_hchildkey, mytag(_name)); }
	virtual void next()
		{ _end = true; }
	virtual bool end()
		{ return _end || _hchildkey == 0; }
};

key_named_child_enum::key_named_child_enum(HKEY hkey, const string & name) :
	_name(name)
{
	if (ERROR_SUCCESS != RegOpenKeyEx(hkey, name.c_str(), 0, KEY_ALL_ACCESS, &_hchildkey))
		_hchildkey = 0;
}

key_named_child_enum::~key_named_child_enum()
{
	if (_hchildkey != 0)
		RegCloseKey(_hchildkey);
}

//////////////////////////////////////
// registry::element implementation //
//////////////////////////////////////

inline registry::element::element(const element & src) :
	_tag(src._tag)
{
	if (ERROR_SUCCESS != RegOpenKeyEx(src._hkey, 0, 0, KEY_ALL_ACCESS, &_hkey))
		throwWin32Error("RegOpenKeyEx(duplicate)");
}

inline registry::element::element(HKEY hkey) 
{
	if (ERROR_SUCCESS != RegOpenKeyEx(hkey, 0, 0, KEY_ALL_ACCESS, &_hkey))
		throwWin32Error("RegOpenKeyEx(duplicate)");
}

inline registry::element::element(HKEY hkey, mytag t) :
	_tag(t.name)
{
	if (ERROR_SUCCESS != RegOpenKeyEx(hkey, 0, 0, KEY_ALL_ACCESS, &_hkey))
		throwWin32Error("RegOpenKeyEx(duplicate)");
}

inline registry::element::element(HKEY hkey, subkey s) :
	_tag(basename(s.name))
{
	if (ERROR_SUCCESS != RegOpenKeyEx(hkey, _tag.c_str(), 0, KEY_ALL_ACCESS, &_hkey))
		throwWin32Error("RegOpenKeyEx");
}

inline registry::element::~element() 
{
	if (_hkey != 0)
		RegCloseKey(_hkey);
}

inline const string & registry::element::tag() const
{
	return _tag;
}

inline attribute_enumeration * registry::element::get_attrs() const
{
	return new key_attr_enum(_hkey);
}

inline bool registry::element::get_attribute(const string & name, string & value) const
{
	DWORD cbData = 0;
	if (ERROR_SUCCESS != RegQueryValueEx(_hkey, name.c_str(), 0, 0, 0, &cbData))
		return false;
	value.resize(cbData);
	if (ERROR_SUCCESS != RegQueryValueEx(_hkey, name.c_str(), 0, 0, (LPBYTE)&*value.begin(), &cbData))
		throwWin32Error("RegQueryValueEx");
	value.resize(cbData-1);
	return true;
}

inline void registry::element::set_attribute(const string & name, const string & value)
{
	if (ERROR_SUCCESS 
		!= RegSetValueEx(_hkey, name.c_str(), 0, REG_SZ, (const BYTE *)value.c_str(), value.size()+1))
		throwWin32Error("RegSetValueEx");
}

inline node_enumeration * registry::element::get_children()
{
	return new key_children_enum(_hkey);
}

inline node_enumeration * registry::element::get_children_by_tag(const string & tag)
{
	return new key_named_child_enum(_hkey, tag);
}

inline handle registry::element::create_child(const string & tag)
{
	element * e = new element(tag);
	if (ERROR_SUCCESS != RegCreateKeyEx(_hkey, tag.c_str(), 0, 0, 0, KEY_ALL_ACCESS, 0, &e->_hkey, 0)) {
		delete e;
		throwWin32Error("RegCreateKeyEx");
	}
	return reinterpret_cast<handle>(e);
}

void registry::element::clear(bool attr, bool children)
{
	DWORD max_key_index, max_key_length, max_value_index, max_value_length;
	if (ERROR_SUCCESS
		!= RegQueryInfoKey(_hkey, 0, 0, 0, &max_key_index, &max_key_length, 0, 
			&max_value_index, &max_value_length, 0, 0, 0))
		throwWin32Error("RegQueryInfoKey");
	string buffer;
	buffer.resize(max(max_key_length, max_value_length)+1);

	if (attr) {
		for (; max_value_index; --max_value_index) {
			DWORD cValueName = buffer.size();
			if (ERROR_SUCCESS
				!= RegEnumValue(_hkey, 0, &*buffer.begin(), &cValueName, 0, 0, 0, 0))
				throwWin32Error("RegEnumValue");
			if (ERROR_SUCCESS
				!= RegDeleteValue(_hkey, buffer.substr(0, cValueName).c_str()))
				throwWin32Error("RegDeleteValue");
		}
	}

	if (children) {
		for (; max_key_index; --max_key_index) {
			DWORD cName = buffer.size();
			if (ERROR_SUCCESS
				!= RegEnumKeyEx(_hkey, 0, &*buffer.begin(), &cName, 0, 0, 0, 0))
				throwWin32Error("RegEnumKeyEx");
			if (ERROR_SUCCESS
				!= RegDeleteKey(_hkey, buffer.substr(0, cName).c_str()))
				throwWin32Error("RegDeleteKey");
		}
	}
}

inline void registry::element::erase(const string & subkey)
{
	if (ERROR_SUCCESS != RegDeleteKey(_hkey, subkey.c_str()))
		throwWin32Error("RegDeleteKey");
}

inline registry::element::element(const string & tag) :
	_hkey(0), _tag(tag)
{
}

inline string registry::element::basename(const string & subkey)
{
	size_t bpos = subkey.rfind('\\');
	if (bpos == size_t(std::string::npos))
		return subkey;
	else
		return subkey.substr(bpos+1);
}

//////////////
// registry //
//////////////

registry::registry(HKEY hkey)
{
	_root = new element(hkey);
}

registry::registry(HKEY hkey, const string & name)
{
	_root = new element(hkey, subkey(name));
}

registry::~registry()
{
	delete _root;
}

node registry::root()
{
	return create_node(copy(_root));
}

const_node registry::root() const
{
	return create_node(const_cast<registry*>(this)->copy(_root));
}

void registry::erase(const node & n, const string & subkey)
{
	reinterpret_cast<element*>(get_handle(n))->erase(subkey);
}

handle registry::copy(handle h)
{
	return reinterpret_cast<handle>(new element(*reinterpret_cast<element*>(h)));
}

void registry::release(handle h)
{
	delete reinterpret_cast<element*>(h);
}

bool registry::null(handle h)
{
	return h == 0;
}

string registry::get_tag(handle h)
{
	return reinterpret_cast<element*>(h)->tag();
}

attribute_enumeration * registry::get_attributes(handle h)
{
	return reinterpret_cast<element*>(h)->get_attrs();
}

bool registry::get_attribute(handle h, const string & name, string & value)
{
	return reinterpret_cast<element*>(h)->get_attribute(name, value);
}

void registry::set_attribute(handle h, const string & name, const string & value)
{
	reinterpret_cast<element*>(h)->set_attribute(name, value);
}

node_enumeration * registry::get_children(handle h)
{
	return reinterpret_cast<element*>(h)->get_children();
}

node_enumeration * registry::get_children_by_tag(handle h, const string & tag)
{
	return reinterpret_cast<element*>(h)->get_children_by_tag(tag);
}

handle registry::create_child(handle h, const string & tag)
{
	return reinterpret_cast<element*>(h)->create_child(tag);
}

void registry::clear(handle h, bool attr, bool children)
{
	reinterpret_cast<element*>(h)->clear(attr, children);
}
	
void registry::erase(handle h)
{
	//don't know how it is possible
}

} //namespace xml